 /*
  * Mirante Tecnologia
  * Copyright 2010, Mirante Informatica LTDA, 
  * and individual contributors as indicated by the @authors tag
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this software; if not, write to the Free
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
package com.miranteinfo.seam.core;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;
import org.hibernate.event.AutoFlushEventListener;
import org.hibernate.event.DeleteEventListener;
import org.hibernate.event.EventListeners;
import org.hibernate.event.FlushEventListener;
import org.hibernate.event.MergeEventListener;
import org.hibernate.event.PersistEventListener;
import org.hibernate.event.ReplicateEventListener;
import org.hibernate.event.SaveOrUpdateEventListener;
import org.hibernate.impl.SessionFactoryImpl;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.persistence.EntityManagerFactory;
import org.jboss.seam.persistence.HibernateSessionFactory;

import com.miranteinfo.seam.hibernate.DirtyListener;
import com.miranteinfo.seam.hibernate.HibernateAutoFlushEventListener;
import com.miranteinfo.seam.hibernate.HibernateFlushEventListener;
import com.miranteinfo.seam.hibernate.SessionFactoryProxy;
import com.miranteinfo.seam.hibernate.envers.EnversConfiguration;

/**
 * Classe responsavel por configurar as sessoes do hibernate.
 * 
 * @author lucas lins
 *
 */
@Name("com.miranteinfo.seam.core.init")
public class Init {
	
	private static final Log log = LogFactory.getLog(Init.class);
	
	@Observer("org.jboss.seam.postInitialization")
	public void configure() throws Exception{
		
		boolean sessionFactoryConfigured = false;
		Object component = null;
		
		for(String name : Contexts.getApplicationContext().getNames()){
			component = Contexts.getApplicationContext().get(name);
			
			if(component instanceof EntityManagerFactory){
				
				javax.persistence.EntityManagerFactory emf = ((EntityManagerFactory) component).getEntityManagerFactory();
				Field field = emf.getClass().getDeclaredField("sessionFactory");
				field.setAccessible(true);
				Object sf = field.get(emf);
				field.set(emf, Proxy.newProxyInstance(Init.class.getClassLoader(), sf.getClass().getInterfaces(), new SessionFactoryProxy((SessionFactory) sf)));
				configure((SessionFactoryImpl) sf);
				sessionFactoryConfigured = true;
				
			}else if(component instanceof HibernateSessionFactory){
				
				HibernateSessionFactory hsf = (HibernateSessionFactory) component;
				Field field = HibernateSessionFactory.class.getDeclaredField("sessionFactory");
				field.setAccessible(true);
				SessionFactory sf = hsf.getSessionFactory();
				field.set(hsf, Proxy.newProxyInstance(Init.class.getClassLoader(), sf.getClass().getInterfaces(), new SessionFactoryProxy(sf)));
				configure((SessionFactoryImpl) sf);
				sessionFactoryConfigured = true;
				
			}
			
		}
		
		if(!sessionFactoryConfigured){
			log.warn("No SessionFactory is configured...");
		}
		
	}
	
	private void configure(SessionFactoryImpl sessionFactory){
		EventListeners eventListeners = sessionFactory.getEventListeners();
		this.setFlushEventListners(eventListeners, sessionFactory);
		this.setAutoFlushEventListners(eventListeners, sessionFactory);
		this.setSaveEventListeners(eventListeners);
		this.setSaveOrUpdateEventListeners(eventListeners);
		this.setUpdateEventListeners(eventListeners);
		this.setPersistEventListeners(eventListeners);
		this.setPersistOnFlushEventListeners(eventListeners);
		this.setDeleteEventListeners(eventListeners);
		this.setReplicateEventListeners(eventListeners);
		this.setMergeEventListeners(eventListeners);
		this.setSaveOrUpdateCopyEventListeners(eventListeners);
	}
	
	
	private void setFlushEventListners(EventListeners eventListeners, SessionFactoryImpl sessionFactory){
		eventListeners.setFlushEventListeners(new FlushEventListener[]{new HibernateFlushEventListener(new EnversConfiguration(sessionFactory))});
	}
	
	private void setAutoFlushEventListners(EventListeners eventListeners, SessionFactoryImpl sessionFactory){
		eventListeners.setAutoFlushEventListeners(new AutoFlushEventListener[]{new HibernateAutoFlushEventListener(new EnversConfiguration(sessionFactory))});
	}
	
	private void setSaveEventListeners(EventListeners eventListeners){
		SaveOrUpdateEventListener[] listeners = eventListeners.getSaveEventListeners();
		eventListeners.setSaveEventListeners(addToArray(SaveOrUpdateEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setSaveOrUpdateEventListeners(EventListeners eventListeners){
		SaveOrUpdateEventListener[] listeners = eventListeners.getSaveOrUpdateEventListeners();
		eventListeners.setSaveOrUpdateEventListeners(addToArray(SaveOrUpdateEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setUpdateEventListeners(EventListeners eventListeners){
		SaveOrUpdateEventListener[] listeners = eventListeners.getUpdateEventListeners();
		eventListeners.setUpdateEventListeners(addToArray(SaveOrUpdateEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setPersistEventListeners(EventListeners eventListeners){
		PersistEventListener[] listeners = eventListeners.getPersistEventListeners();
		eventListeners.setPersistEventListeners(addToArray(PersistEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setPersistOnFlushEventListeners(EventListeners eventListeners){
		PersistEventListener[] listeners = eventListeners.getPersistOnFlushEventListeners();
		eventListeners.setPersistOnFlushEventListeners(addToArray(PersistEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setDeleteEventListeners(EventListeners eventListeners){
		DeleteEventListener[] listeners = eventListeners.getDeleteEventListeners();
		eventListeners.setDeleteEventListeners(addToArray(DeleteEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setReplicateEventListeners(EventListeners eventListeners){
		ReplicateEventListener[] listeners = eventListeners.getReplicateEventListeners();
		eventListeners.setReplicateEventListeners(addToArray(ReplicateEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setMergeEventListeners(EventListeners eventListeners){
		MergeEventListener[] listeners = eventListeners.getMergeEventListeners();
		eventListeners.setMergeEventListeners(addToArray(MergeEventListener.class, new DirtyListener(), listeners));
	}
	
	private void setSaveOrUpdateCopyEventListeners(EventListeners eventListeners){
		MergeEventListener[] listeners = eventListeners.getSaveOrUpdateCopyEventListeners();
		eventListeners.setSaveOrUpdateCopyEventListeners(addToArray(MergeEventListener.class, new DirtyListener(), listeners));
	}
	
	@SuppressWarnings("unchecked")
	private <T> T[] addToArray(Class<T> type, T listener, T[] listeners){
		T[] newArray = (T[])Array.newInstance(type, listeners.length + 1);
		for(int i = 0; i < listeners.length; i++) newArray[i] = listeners[i];
		newArray[listeners.length] = listener;
		return newArray;
	}
	
}
